package de.tuhh.simsearch.controller;

import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import org.apache.commons.codec.digest.DigestUtils;

import de.tuhh.simsearch.constants.Constants;
import de.tuhh.simsearch.constants.Constants.FilterType;
import de.tuhh.simsearch.constants.Constants.SearchTypes;
import de.tuhh.simsearch.controller.contentmgmt.interfaces.ContentControllerInterface;
import de.tuhh.simsearch.dto.CategoryDTO;
import de.tuhh.simsearch.dto.FileDTO;
import de.tuhh.simsearch.dto.GroupDTO;
import de.tuhh.simsearch.dto.PasswordDTO;
import de.tuhh.simsearch.dto.PreferenceDTO;
import de.tuhh.simsearch.dto.UserDTO;
import de.tuhh.simsearch.model.persistence.contentmgmt.mltools.interfaces.IndexingConstants;
import de.tuhh.simsearch.model.persistence.usermgmt.eao.interfaces.UserMgmtEAOInterface;
import de.tuhh.simsearch.model.persistence.usermgmt.entities.GroupEntity;
import de.tuhh.simsearch.model.persistence.usermgmt.entities.PreferenceEntity;
import de.tuhh.simsearch.model.persistence.usermgmt.entities.UserEntity;
import de.tuhh.simsearch.util.Conversion;

/**
 * Session Bean implementation class RequestHandler. This session bean handles all requests from clients which are
 * specified in the local interface {@link RequestHandlerLocal}. The request handler checks privileges, communicates
 * with the databases, calls methods in the content project and transforms requests from 'client language' into the
 * 'backend language'. This provides additional security and prevents the client from obtaining too much internal
 * information the client is not allowed to access or should not get.
 * 
 * @author SimSearch 1
 * 
 * @see RequestHandlerLocal
 * @see ContentControllerInterface
 */
@Stateless
@LocalBean
public class RequestHandler implements RequestHandlerLocal {

  /**
   * The Entity Access Object to be used to access database
   */
  @EJB UserMgmtEAOInterface usrmEAO;

  /**
   * The conversion EJB to convert DTOs and Entities
   */
  @EJB Conversion conv;

  /**
   * The content controller bean to access content database and indexing
   */
  @EJB ContentControllerInterface contentController;

  /**
   * Mapping of the search types to string values
   */
  private static final HashMap<SearchTypes, String> searchTypesMap;

  /**
   * Default constructor.
   */
  public RequestHandler() {

  }

  /**
   * Constructor for testing
   * 
   * @param eao
   *          the user-management-eao to use
   * @param conv
   *          the conversion-class to use
   */
  public RequestHandler(UserMgmtEAOInterface eao, Conversion conv) {

    this.usrmEAO = eao;
    this.conv = conv;
  }

  @Override
  public List<UserDTO> searchUsers(String srchStrng) {

    LinkedList<UserDTO> usrdtos = new LinkedList<UserDTO>();
    List<String> usrs = usrmEAO.searchInUsers(srchStrng);

    if (usrs != null && !usrs.isEmpty()) {
      for (String u : usrs) {

        UserDTO udto = new UserDTO(u);
        usrdtos.add(udto);
      }
    }
    return usrdtos;
  }

  @Override
  public List<GroupDTO> searchGroups(String srchStrng) {

    LinkedList<GroupDTO> grpdtos = new LinkedList<GroupDTO>();
    List<String> grps = usrmEAO.searchInGroups(srchStrng);

    if (grps != null && !grps.isEmpty()) {
      for (String g : grps) {

        GroupDTO gdto = new GroupDTO();
        gdto.setName(g);
        grpdtos.add((gdto));
      }
    }

    return grpdtos;
  }

  @Override
  public List<PreferenceDTO> searchPreferences(String srchStrng) {

    LinkedList<PreferenceDTO> prefdtos = new LinkedList<PreferenceDTO>();
    List<PreferenceEntity> preferences = usrmEAO.searchInPreferences(srchStrng);

    if (preferences != null && !preferences.isEmpty()) {
      for (PreferenceEntity p : preferences) {

        PreferenceDTO pdto = conv.fromEntity(p);
        prefdtos.add((pdto));
      }
    }

    return prefdtos;
  }

  @Override
  public List<FileDTO> searchContent(String srchStrng, FilterType filter, String principal, Set<SearchTypes> searchTypes) {

    String[] fields = mapFields(searchTypes);

    List<FileDTO> fdto = contentController.searchFiles(srchStrng, fields, true);

    fdto = filterResults(fdto, filter, principal);

    return fdto;
  }

  // @Override
  // public List<FileDTO> searchContent(String principal, FileDTO dto, InputStream is) {
  //
  // FileDTO fdto = uploadFile(dto, is);
  // List<FileDTO> result = null;
  //
  // result = contentController.searchFiles(fdto);
  // // NEXTRELEASE:Maschine learning
  // return result;
  // }

  @Override
  public List<CategoryDTO> searchCategories(String searchString) {

    return contentController.searchCategories(searchString);
  }

  @Override
  public List<GroupDTO> getJoinedGroups(String usrName) {

    UserEntity usr = usrmEAO.getUserEntity(usrName);
    LinkedList<GroupDTO> list = new LinkedList<GroupDTO>();

    Set<GroupEntity> joinedGroups = usr.getJoinedGroups();
    if (joinedGroups != null && !joinedGroups.isEmpty()) {

      for (GroupEntity g : joinedGroups) {
        list.add(conv.fromEntity(g));
      }
    }

    Set<GroupEntity> createdGroups = usr.getCreatedGroups();
    if (createdGroups != null && !createdGroups.isEmpty()) {

      for (GroupEntity g : createdGroups) {
        list.add(conv.fromEntity(g));
      }
    }
    return list;
  }

  @Override
  public List<PreferenceDTO> getPreferences(String usrName) {

    UserEntity usr = usrmEAO.getUserEntity(usrName);
    LinkedList<PreferenceDTO> list = new LinkedList<PreferenceDTO>();

    if (!usr.getPreferences().isEmpty()) {
      Set<PreferenceEntity> prefs = usr.getPreferences();
      for (PreferenceEntity p : prefs) {
        list.add(conv.fromEntity(p));
      }
      return list;
    }
    return null;
  }

  @Override
  public List<GroupDTO> getRequestedGroups(String usrName) {

    UserEntity usr = usrmEAO.getUserEntity(usrName);
    LinkedList<GroupDTO> list = new LinkedList<GroupDTO>();

    if (!usr.getRequestedGroups().isEmpty()) {
      Set<GroupEntity> groups = usr.getRequestedGroups();
      for (GroupEntity g : groups) {
        list.add(conv.fromEntity(g));
      }
      return list;
    }
    return null;
  }

  @Override
  public boolean generateGroup(String principal, GroupDTO grpdto) {

    boolean success = false;
    GroupEntity group = null;

    if (grpdto.getGrpAdmin().equals(principal)) {
      if (usrmEAO.isGroupNameFree(grpdto.getName())) {
        group = new GroupEntity(grpdto.getName());
        success = usrmEAO.persistGroup(group);
        group.setAdministrator(usrmEAO.getUserEntity(grpdto.getGrpAdmin()));
      }
    }

    return success;
  }

  @Override
  public GroupDTO getGroup(String grpName) {

    return conv.fromEntity(usrmEAO.getGroupEntity(grpName));
  }

  @Override
  public boolean isUserGroupAdmin(String name, String gName) {

    boolean success = false;
    GroupEntity entity = usrmEAO.getGroupEntity(gName);

    if (entity.getAdministrator().getName().equals(name)) {
      success = true;
    }
    return success;
  }

  @Override
  public boolean isUserContentAdmin(String name) {

    return usrmEAO.isContentAdmin(name);
  }

  @Override
  public boolean acceptUserToGroup(String principal, String grpName, String usrName) {

    boolean success = false;
    GroupEntity entity = usrmEAO.getGroupEntity(grpName);

    if (entity != null) {
      if (entity.getAdministrator().getName().equals(principal)) {
        success = usrmEAO.removeUserFromGroup(usrName, grpName);
        if (success) {
          success = usrmEAO.addUserToGroup(usrName, grpName, false);
        }
      }
    }

    return success;
  }

  @Override
  public boolean addUserAsPendingToGroup(String principal, String grpName) {

    boolean success = false;
    GroupEntity entity = usrmEAO.getGroupEntity(grpName);
    if (entity != null) {
      success = usrmEAO.addUserToGroup(principal, grpName, true);
    }
    return success;
  }

  @Override
  public boolean removeUserFromGroup(String principal, String name, String gName) {

    boolean success = false;
    GroupEntity entity = usrmEAO.getGroupEntity(gName);
    UserEntity usrEntity = usrmEAO.getUserEntity(name);

    if (entity != null) {

      if (entity.getAdministrator().getName().equals(principal)) {
        if (!entity.getAdministrator().getName().equals(name)) {
          success = usrmEAO.removeUserFromGroup(name, gName);
        }

      } else {
        if (principal.equals(name)) {
          if (entity.getMembers().contains(usrEntity)) {
            success = usrmEAO.removeUserFromGroup(name, gName);
          }
        }

      }
    }
    return success;
  }

  @Override
  public boolean isUserInGroup(String name, String gName) {

    boolean userInGroup = false;
    UserEntity uEntity = usrmEAO.getUserEntity(name);
    GroupEntity gEntity = usrmEAO.getGroupEntity(gName);
    if (uEntity.getJoinedGroups().contains(gEntity)) {
      userInGroup = true;
    } else {
      if (uEntity.getCreatedGroups().contains(gEntity)) {
        userInGroup = true;
      }
    }
    return userInGroup;
  }

  @Override
  public List<UserDTO> getGroupMembers(String grpName) {

    GroupEntity grp = usrmEAO.getGroupEntity(grpName);
    LinkedList<UserDTO> list = new LinkedList<UserDTO>();

    if (!grp.getMembers().isEmpty()) {
      Set<UserEntity> mbrs = grp.getMembers();
      for (UserEntity u : mbrs) {
        list.add(conv.fromEntity(u));
      }
      return list;
    }
    return null;
  }

  @Override
  public UserDTO getUser(String usrName) {

    return conv.fromEntity(usrmEAO.getUserEntity(usrName));
  }

  @Override
  public PreferenceDTO getPreference(String prefName, String contentType) {

    return conv.fromEntity(usrmEAO.getPreferenceEntity(prefName, Constants.descContentIdMap.get(contentType)));
  }

  @Override
  public int register(UserDTO user, PasswordDTO pwdto) {

    int success = 3;

    if (usrmEAO.getUserEntity(user.getName()) != null) {
      success = 1;
    } else {
      UserEntity entity = new UserEntity(user.getName());
      if (pwdto.getNewPassword().equals(pwdto.getRepeatNewPassword())) {
        entity.setPassword(DigestUtils.sha256Hex(pwdto.getNewPassword()));
        entity.addRole(usrmEAO.getRoleEntity(Constants.UserRole));
        usrmEAO.persistUser(entity);
        success = 0;
      } else {
        success = 2;
      }
    }
    return success;
  }

  // IFTIME: Add admin functionality?
  @Override
  public boolean updatePassword(String principal, UserDTO user, PasswordDTO pwDto) {

    boolean isAuthorized = false;
    boolean success = false;

    if (user.getName().equals(principal)) {
      isAuthorized = true;
    }

    if (isAuthorized) {
      UserEntity usr = usrmEAO.getUserEntity(user.getName());
      if (DigestUtils.sha256Hex(pwDto.getOldPassword()).equals(usr.getPassword())) {
        if (pwDto.getNewPassword().equals(pwDto.getRepeatNewPassword())) {
          String hashNewPasswd = DigestUtils.sha256Hex(pwDto.getNewPassword());
          usr.setPassword(hashNewPasswd);
          if (usrmEAO.getUserEntity(usr.getName()).getPassword().equals(hashNewPasswd)) {
            success = true;
          }
        }
      }
    } else {
      success = false;
    }

    return success;
  }

  // IFTIME: add admin functionality
  @Override
  public boolean deleteUser(String principal, String usr) {

    boolean isAuthorized = false;
    boolean success = true;

    if (principal.equals(usr)) {
      isAuthorized = true;
    }

    if (isAuthorized) {
      if (!usrmEAO.deleteUser(usr)) {
        success = false;
      }
    } else {
      success = false;
    }

    return success;
  }

  @Override
  public boolean deleteGroup(String principal, String grp) {

    boolean isAuthorized = false;
    boolean success = true;
    GroupEntity entity = usrmEAO.getGroupEntity(grp);

    if (principal.equals(entity.getAdministrator().getName())) {
      isAuthorized = true;
    } else {
      if (usrmEAO.getUserEntity(principal).getRoles().contains(usrmEAO.getRoleEntity("ADMIN"))) {
        isAuthorized = true;
      }
    }

    if (isAuthorized) {

      if (entity.getMembers() != null && entity.getMembers().size() > 0) {
        
        List<UserEntity> tmpu1 = new ArrayList<UserEntity>();
        tmpu1.addAll(entity.getMembers());
        for (UserEntity u : tmpu1) {
          entity.removeMember(u);
        }
      }

      if (entity.getPendingUsers() != null && entity.getPendingUsers().size() > 0) {
        
        List<UserEntity> tmpu2 = new ArrayList<UserEntity>();
        tmpu2.addAll(entity.getPendingUsers());
        for (UserEntity u : tmpu2) {
          entity.removePendingUser(u);
        }
      }

      if (entity.getAdministrator() != null) {

        entity.removeAdministrator();
      }

      if (!usrmEAO.deleteGroup(grp)) {

        success = false;
      }
    } else {

      success = false;
    }

    return success;
  }

  @Override
  public boolean deletePreference(String principal, PreferenceDTO pdto) {

    boolean isPossible = false;

    PreferenceEntity prefent = usrmEAO.getPreferenceEntity(pdto.getName(),
        Constants.descContentIdMap.get(pdto.getContentType()));
    UserEntity uentity = usrmEAO.getUserEntity(principal);
    Set<PreferenceEntity> list = uentity.getPreferences();

    if (principal != null && prefent != null) {
      if (list.contains(prefent)) {
        isPossible = true;
      }
    }

    if (isPossible) {
      uentity.removePreference(prefent);
    }

    return isPossible;
  }

  @Override
  public boolean addPreferenceToUser(String principal, PreferenceDTO prefToAdd) {

    boolean success = false;

    success = usrmEAO.addPreference(principal, prefToAdd.getName(),
        Constants.descContentIdMap.get(prefToAdd.getContentType()));

    return success;
  }

  @Override
  public FileDTO uploadFile(FileDTO dto, InputStream is) {

    return contentController.uploadFile(dto, is);
  }

  @Override
  public InputStream downloadFile(FileDTO dto) {

    return contentController.downloadFile(dto.getId());
  }

  @Override
  public InputStream downloadFile(BigInteger id) {

    return contentController.downloadFile(id);
  }

  @Override
  public FileDTO getFileInfo(BigInteger id) {

    return contentController.getFileInfo(id);
  }

  @Override
  public boolean generateCategory(String principal, CategoryDTO dto) {

    boolean success = false;

    if (usrmEAO.getUserEntity(principal).getRoles().contains(usrmEAO.getRoleEntity("ADMIN"))) {
      success = contentController.addCategory(dto);
      if (success) {
        PreferenceDTO pref = new PreferenceDTO();
        pref.setContentType(dto.getContentType());
        pref.setName(dto.getCategoryName());
        success = generatePreference(pref);
      }
    }
    return success;
  }

  /*
   * ----------------------------PRIVATE METHODS----------------------------------
   */

  static {
    searchTypesMap = new HashMap<SearchTypes, String>();
    searchTypesMap.put(SearchTypes.CATEGORIES, IndexingConstants.CATEGORY_FIELD);
    searchTypesMap.put(SearchTypes.CONTENT, IndexingConstants.CONTENT_FIELD);
    searchTypesMap.put(SearchTypes.DESCRIPTION, IndexingConstants.DESCRIPTION_FIELD);
    searchTypesMap.put(SearchTypes.METADATA, IndexingConstants.META_FIELD);
    searchTypesMap.put(SearchTypes.NAMES, IndexingConstants.FILENAME_FIELD);
  }

  private String[] mapFields(Set<SearchTypes> searchType) {

    String[] fields;

    if (searchType != null) {
      fields = new String[searchType.size()];

      int i = 0;
      for (SearchTypes s : searchType) {
        fields[i] = searchTypesMap.get(s);
        i++;
      }
    } else {
      fields = IndexingConstants.FIELDS;
    }
    return fields;
  }

  private List<FileDTO> filterResults(List<FileDTO> fdto, FilterType filter, String principal) {

    switch (filter) {
      case BOTH:
        fdto = searchContentFilterPreferences(principal, fdto);
        fdto = searchContentFilterTrustedUsers(principal, fdto);
        break;
      case PREFERENCES:
        fdto = searchContentFilterPreferences(principal, fdto);
        break;
      case TRUSTFUL_USERS:
        fdto = searchContentFilterTrustedUsers(principal, fdto);
        break;
      default:
        break;
    }
    return fdto;
  }

  private List<FileDTO> searchContentFilterTrustedUsers(String principal, List<FileDTO> unfilteredList) {

    List<FileDTO> result = new LinkedList<FileDTO>();
    UserEntity principalEntity = usrmEAO.getUserEntity(principal);

    Set<UserEntity> trustedUsers = new HashSet<UserEntity>();
    Set<GroupEntity> groups = new HashSet<GroupEntity>();

    trustedUsers.add(principalEntity);

    groups.addAll(principalEntity.getJoinedGroups());
    groups.addAll(principalEntity.getCreatedGroups());

    if (groups != null) {
      for (GroupEntity g : groups) {
        if (g.getMembers() != null) {
          trustedUsers.addAll(g.getMembers());
        }
        trustedUsers.add(g.getAdministrator());
      }
    }

    for (UserEntity u : trustedUsers) {
      for (FileDTO f : unfilteredList) {
        if (f.getOwner().equals(u.getName())) {
          result.add(f);
        }
      }
    }

    return result;
  }

  private List<FileDTO> searchContentFilterPreferences(String principal, List<FileDTO> unfilteredList) {

    List<FileDTO> resultList = new LinkedList<FileDTO>();
    UserEntity user = usrmEAO.getUserEntity(principal);

    for (FileDTO f : unfilteredList) {
      for (String c : f.getCategories()) {
        for (PreferenceEntity p : user.getPreferences()) {
          if (p.getName().equals(c) && p.getContentID() == Constants.descContentIdMap.get(f.getContentType())) {
            resultList.add(f);
          }
        }
      }

    }

    return resultList;
  }

  private boolean generatePreference(PreferenceDTO pref) {

    boolean success = true;

    if (usrmEAO.getPreferenceEntity(pref.getName(), Constants.descContentIdMap.get(pref.getContentType())) != null) {
      success = false;
    } else {
      PreferenceEntity entity = new PreferenceEntity(pref.getName(), Constants.descContentIdMap.get(pref
          .getContentType()));
      usrmEAO.persistNewPreference(entity);
    }

    return success;
  }

}
